ci(e2e): report Cypress retries in CI so flaky specs are visible - #3033
ci(e2e): report Cypress retries in CI so flaky specs are visible#3033fra-shipper wants to merge 3 commits into
Conversation
cypress.config.ts sets retries: 3, so a spec that fails and then passes on retry reports the job green with no trace anywhere a human will look (Chainlit#3023 was a real oauth_auth failure absorbed this way). Extend the after:spec handler, which previously discarded both of its arguments, to record tests where an attempt failed but the spec still passed, and write them to cypress/reports/retries.json per shard. Each e2e-tests.yaml matrix job now uploads that file as an artifact, and a new report-retries job downloads every shard's artifact, merges them, and writes a table to $GITHUB_STEP_SUMMARY. When a PR has retries, the job also posts or updates a best-effort PR comment; on forked PRs GITHUB_TOKEN is read-only, so the comment is skipped and the step summary remains the only, always-available output. Tested with cypress/support/retryReport.test.ts (node --test), which fails against the previous discard-everything behavior and passes against the new detection logic.
ci.yaml's e2e-tests: job called e2e-tests.yaml with no permissions
override, so it inherited ci.yaml's top-level `permissions: read-all`.
GitHub Actions permissions can only be downgraded through a
reusable-workflow call chain, never elevated, so report-retries'
own `permissions: {contents: read, pull-requests: write}` never
actually took effect: the PR-comment calls were always going to 403
and get swallowed by the existing core.warning(), even on same-repo
PRs. Add the matching permissions block to the caller job so the
grant can flow through.
Also narrow tsconfig.json's new exclude from `cypress/**/*.test.ts`
to the one file that needs it, so a future cypress *.test.ts file
doesn't silently lose type-checking.
There was a problem hiding this comment.
1 issue found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cypress/support/retryReport.test.ts">
<violation number="1" location="cypress/support/retryReport.test.ts:9">
P2: This regression test is never executed: nothing in any CI workflow or npm/pnpm script runs `node --test cypress/support/retryReport.test.ts`, and tsconfig.json excludes the file. The retry-detection logic therefore has no automated coverage in CI, so a future change to collectRetriedTests can silently regress. Wire the test into CI (e.g. a workflow step or a `pnpm test` script entry) so it actually runs on every PR.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const asTests = (tests: unknown) => | ||
| tests as CypressCommandLine.RunResult['tests']; | ||
|
|
||
| test('ignores a test that passed on its first attempt', () => { |
There was a problem hiding this comment.
P2: This regression test is never executed: nothing in any CI workflow or npm/pnpm script runs node --test cypress/support/retryReport.test.ts, and tsconfig.json excludes the file. The retry-detection logic therefore has no automated coverage in CI, so a future change to collectRetriedTests can silently regress. Wire the test into CI (e.g. a workflow step or a pnpm test script entry) so it actually runs on every PR.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/support/retryReport.test.ts, line 9:
<comment>This regression test is never executed: nothing in any CI workflow or npm/pnpm script runs `node --test cypress/support/retryReport.test.ts`, and tsconfig.json excludes the file. The retry-detection logic therefore has no automated coverage in CI, so a future change to collectRetriedTests can silently regress. Wire the test into CI (e.g. a workflow step or a `pnpm test` script entry) so it actually runs on every PR.</comment>
<file context>
@@ -0,0 +1,63 @@
+const asTests = (tests: unknown) =>
+ tests as CypressCommandLine.RunResult['tests'];
+
+test('ignores a test that passed on its first attempt', () => {
+ const tests = asTests([
+ {
</file context>
- cypress.config.ts: guard after:spec against undefined results, which cypress passes in interactive (`cypress open`) mode; the previous code threw before killChainlit() could run. - e2e-tests.yaml: reconcile the marker PR comment on every run instead of returning early when a run has zero retries, so a fixed run clears the stale "N tests retried" comment; paginate the comment lookup so the marker is found on PRs with more than one page of comments; fix "test attempt(s)" wording to "test(s)" since each retries[] entry is one test, not one attempt. - tsconfig.json: stop excluding retryReport.test.ts from type checking; enable allowImportingTsExtensions + noEmit instead so the file (which imports with an explicit .ts extension for node's native type-stripping test runner) type-checks cleanly. - package.json / tests.yaml: wire retryReport.test.ts into CI via a new test:unit script (node --test) run once from the existing "Unit & integration tests" workflow.
|
Addressed the review findings in 209adbf:
Verified: |
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/e2e-tests.yaml">
<violation number="1" location=".github/workflows/e2e-tests.yaml:155">
P2: When a shard is killed before `after:spec`, or retry-artifact download fails, this branch treats the incomplete collection as a clean run and overwrites an existing PR comment with “No specs needed a retry.” Reconcile to zero retries only after confirming all expected shard reports were collected; otherwise leave the previous report unchanged and surface the collection failure.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| const marker = '<!-- cypress-retry-report -->'; | ||
| const body = | ||
| retries.length === 0 |
There was a problem hiding this comment.
P2: When a shard is killed before after:spec, or retry-artifact download fails, this branch treats the incomplete collection as a clean run and overwrites an existing PR comment with “No specs needed a retry.” Reconcile to zero retries only after confirming all expected shard reports were collected; otherwise leave the previous report unchanged and surface the collection failure.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/e2e-tests.yaml, line 155:
<comment>When a shard is killed before `after:spec`, or retry-artifact download fails, this branch treats the incomplete collection as a clean run and overwrites an existing PR comment with “No specs needed a retry.” Reconcile to zero retries only after confirming all expected shard reports were collected; otherwise leave the previous report unchanged and surface the collection failure.</comment>
<file context>
@@ -152,26 +151,34 @@ jobs:
const marker = '<!-- cypress-retry-report -->';
- const body = `${marker}\n**${retries.length} Cypress test attempt(s) were retried in this run.** See the job summary for details.`;
+ const body =
+ retries.length === 0
+ ? `${marker}\n**No specs needed a retry in this run.**`
+ : `${marker}\n**${retries.length} Cypress test(s) were retried in this run.** See the job summary for details.`;
</file context>
Fixes #3024.
Root cause
cypress.config.tssetsretries: 3, so a spec that fails and then passes on retry reports the job green with no trace anywhere a human will look. #3023 is a concrete example: a genuineoauth_authfailure onwindows-latest-3was absorbed by a retry, and finding it required pulling and grepping job logs by hand. The existingafter:spechandler discarded both of its arguments and only calledkillChainlit(), so this information was never captured.Fix
Implements Option B from the issue (self-contained, no Cypress Cloud dependency, works on forked PRs):
cypress/support/retryReport.tsaddscollectRetriedTests, a pure function that flags a test as retried when its overall state ispassedbut at least one attempt hasstate === 'failed'. Tests that exhaust every retry and still fail are excluded, since those are already visible via the job's exit status.cypress.config.ts'safter:spechandler now receives(spec, results), accumulates retried tests for the shard, and writes them tocypress/reports/retries.jsonafter every spec (so a killed job still leaves partial data)..github/workflows/e2e-tests.yamluploads that file as a per-shard artifact (cypress-retries-<os>-<containers>, same pattern as the existing screenshot upload), and a newreport-retriesjob downloads every shard's artifact, merges them, and writes a table to$GITHUB_STEP_SUMMARY.permissions: read-allis unchanged; thereport-retriesjob itself is grantedpull-requests: write, and.github/workflows/ci.yaml'se2e-tests:call site now also grantscontents: read/pull-requests: writeon the reusable-workflow job entry — a reusable workflow can only narrow permissions inherited from its caller, never widen them, so without this the grant insidee2e-tests.yamlwould have had no effect even on same-repo PRs. On forked PRsGITHUB_TOKENis read-only, so the comment step is skipped ahead of time based onpull_request.head.repo.full_nameand additionally wrapped in try/catch as a fallback; it never usespull_request_target. The step summary is always the primary, unconditional output.The unrelated dead
read-only-bannertestid assertion incypress/e2e/thread_resume/spec.cy.tsflagged in the issue as a separate item is left untouched, as the issue itself scopes it out.Testing
node --test cypress/support/retryReport.test.ts-> 3/3 pass (a test passing on first attempt is ignored, a test that fails then passes is reported with its attempt count, a test that fails every attempt is ignored). Verified this is a genuine regression test: temporarily revertedcollectRetriedTeststo a no-op returning[](matching the old discard-everythingafter:specbehavior) and reran — the retry-detection case failed with anAssertionErroras expected — then restored the real implementation and confirmed 3/3 pass again.npx prettier --checkon all touched files -> clean.npx eslintoncypress.config.ts,cypress/support/retryReport.ts,cypress/support/retryReport.test.ts-> clean.npx tsc --noEmit -p tsconfig.json-> no new errors from this change (only 4 pre-existingTS2428WeakMap errors from Cypress's vendored lodash types, confirmed present identically on a clean checkout viagit stash -u, and unrelated to this change — this tsconfig is also not wired into any CI script;pnpm type-checkonly recurses into thefrontend/libsworkspace packages).actionlinton the modified workflow files -> clean, exit 0.e2e-tests.yamlwas parsed withyaml.safe_loadto confirm it still parses correctly and the job list is['prepare', 'e2e-tests', 'report-retries']as intended.permissions:fix inci.yaml(a reusable-workflow call site can only narrow, never widen, the caller's inherited permissions) was verified by re-reading the affected files against GitHub's documented permission-inheritance semantics forworkflow_call, since that runtime behavior cannot be exercised locally or withactionlint(which validates syntax, not inheritance semantics).lint-staged, which runsformat:files,lint:fix, andactionlinton staged files) ran on every commit and made no further modifications, confirming the diff was already compliant.pnpm test:e2e(the full Cypress suite), since it requires the full Chainlit backend and a live browser; the pure retry-detection logic is covered by thenode:testregression test instead, and the artifact-upload/aggregation job will be exercised directly by this repo's owne2e-testsCI workflow on this PR.Summary by cubic
Makes Cypress retries visible in CI so specs that fail once and pass on retry no longer produce a silently green run (#3024). The workflow now records and reports those tests without changing the pass/fail result; tests that still fail remain governed by the existing job failure.
cypress/reports/retries.json, uploads it, and merges available reports into$GITHUB_STEP_SUMMARY.pull-requests: writethrough the reusable workflow call and adds the retry-report unit tests to CI viapnpm test:unit.Written for commit 209adbf. Summary will update on new commits.